/**
 *  @file: LedTask.cpp
 *  @author: nwz
 *  @brief: 
 *  @date: 2024��8��1��
 * 
 */

#define TAG "led_task"
#include <code/task/LedTask.h>

namespace task {

#define LED_ID_0 0

LedTask::led_t LedTask::leds[] = {
////
		{ LED_ID_0, GPIOC, GPIO_PIN_7 },
////
		};

const LedTask::led_state_t LedTask::states[] = {
		{ LedTask::led_state_id::NULLID },
		////
		{ LedTask::led_state_id::WORK, LedTask::led_state_ops::FOREVER_LOOP,
		LED_ID_0, 0, 0, 500, 500 },
///
		{ LedTask::led_state_id::ERROR, LedTask::led_state_ops::FOREVER_LOOP,
		LED_ID_0, 2, 0, 100, 100 },
		///
		{ LedTask::led_state_id::TIP, LedTask::led_state_ops::LOOP,
		LED_ID_0, 0, 3, 200, 500 },
		///
		{ LedTask::led_state_id::CLOSE_WORK,
				LedTask::led_state_ops::CLOSE_FOREVER,
				LED_ID_0, 0, 0, 100, 100, LedTask::led_state_id::WORK },
		///
		{ LedTask::led_state_id::CLOSE_ERROR,
				LedTask::led_state_ops::CLOSE_FOREVER,
				LED_ID_0, 0, 0, 100, 100, LedTask::led_state_id::ERROR },
		///
		{ LedTask::led_state_id::DIRECT_ON, LedTask::led_state_ops::ON,
		LED_ID_0, 0, 0, 100, 100, LedTask::led_state_id::ERROR },
		///
		{ LedTask::led_state_id::DIRECT_OFF, LedTask::led_state_ops::OFF,
		LED_ID_0, 0, 0, 100, 100, LedTask::led_state_id::ERROR },
//
		};

LedTask::LedTask() {
	// TODO Auto-generated constructor stub
	for (int i = 0; i < ARRAY_SIZE(states); i++) {
		ASSERT((int )states[i].id == i);
	}

	for (int i = 0; i < ARRAY_SIZE(leds); i++) {
		ASSERT((int )leds[i].id == i);
	}
}

LedTask::~LedTask() {
	// TODO Auto-generated destructor stub
}

int LedTask::findOldState(led_t &led, led_state_id state) {
	for (int i = 0; i < ARRAY_SIZE(led.old_state_id); i++) {
		if (led.old_state_id[i] == led.state_id) {
			return i;
		}
	}
	return -1;
}

int LedTask::addOldState(led_t &led, led_state_id state) {
	for (int i = 0; i < ARRAY_SIZE(led.old_state_id); i++) {
		if (led.old_state_id[i] == led_state_id::NULLID) {
			led.old_state_id[i] = state;
			return i;
		}
	}
	LOG_W("led[%d].old_state is too small" NL, led.id);
	return -1;
}

int LedTask::removeOldState(led_t &led, led_state_id id) {
	for (int i = 0; i < ARRAY_SIZE(led.old_state_id); i++) {
		if (led.old_state_id[i] == id) {
			led.old_state_id[i] = led_state_id::NULLID;
			return i;
		}
	}

	return -1;
}

LedTask::led_state_id LedTask::getOldStateId(led_t &led) {
	led_state_id id = led_state_id::NULLID;
	for (int i = 0; i < ARRAY_SIZE(led.old_state_id); i++) {
		if (states[(int) led.old_state_id[i]].id != led_state_id::NULLID) {
			if (id == led_state_id::NULLID) {
				id = states[(int) led.old_state_id[i]].id;
			} else if (states[(int) led.old_state_id[i]].priority
					> states[(int) id].priority) {
				id = states[(int) led.old_state_id[i]].id;
			}
		}
	}
	return id;
}

void LedTask::saveCurrentState(led_t &led) {
	if (led.state_id != led_state_id::NULLID) {
		if (states[(int) led.state_id].ops == led_state_ops::FOREVER_LOOP) {
			bool find = findOldState(led, led.state_id) >= 0 ? true : false;
			if (!find) {
				addOldState(led, led.state_id);
			}
		}
	}
}

int LedTask::setState_2(led_state_id state_id) {
	int ret = -1;
	led_t &led = leds[states[(int) state_id].led_id];
	if (led.state_id == led_state_id::NULLID
			|| states[(int) state_id].priority
					>= states[(int) led.state_id].priority
			|| states[(int) state_id].ops == led_state_ops::CLOSE_FOREVER) {
		saveCurrentState(led);
		led.state_id = state_id;
		led.start_time = NOW();
		ret = 0;
	}

	return ret;
}

uint32_t LedTask::calculateSleepTick() {
	auto ms = portMAX_DELAY;
	for (int i = 0; i < ARRAY_SIZE(leds); i++) {
		if (leds[i].state_id != led_state_id::NULLID) {
			led_t &led = leds[i];
			auto ops = states[(int) leds[i].state_id].ops;
			if (ops == led_state_ops::LOOP
					|| ops == led_state_ops::FOREVER_LOOP) {
				auto on_dur = states[(int) led.state_id].on_duration;
				auto off_dur = states[(int) led.state_id].off_duration;
				auto total = (NOW() - led.start_time) % (on_dur + off_dur);

				auto sleep_time =
						total >= on_dur ?
								on_dur + off_dur - total : on_dur - total;
				if (sleep_time < ms) {
					ms = sleep_time;
				}
			}
		}
	}

	if (ms != portMAX_DELAY)
		ms = pdMS_TO_TICKS(ms);

	return ms;
}

int LedTask::setState(led_state_id state, uint32_t wait_timeout) {
	int ret = 0;
	auto old_now = NOW();
	int rest_time = std::min(wait_timeout, (uint32_t) LED_TASK_MIN_WAIT_TIME);

	while (rest_time > 0) {
		ret = -1;
		if (xSemaphoreTake(mutex,pdMS_TO_TICKS(rest_time)) == pdTRUE) {
			ret = setState_2(state);
			ASSERT(xSemaphoreGive(mutex) == pdTRUE);
		}

		if (ret == 0 || NOW() - old_now >= wait_timeout)
			break;

		rest_time = wait_timeout - (NOW() - old_now);
	}

	if (ret == 0) {
		xSemaphoreGive(sem); ///< notify
		if (rest_time < 1) {
			ret = -2;
		} else {
			if (states[(int) state].ops == led_state_ops::LOOP) {
				auto ms = states[(int) state].loops
						* (states[(int) state].on_duration
								+ states[(int) state].off_duration);
				ASSERT(ms > 0);
				if (rest_time < ms)
					ret = -3;

				ms = std::min(ms, rest_time);
				if (ms > 0)
					DELAY(ms);
			}
		}
	} else {
		ret = -1;
	}

	if (ret) {
		LOG_W("led set failed %d " NL, ret);
	}

	return ret;
}

int LedTask::init() {
	mutex = xSemaphoreCreateMutex();
	ASSERT(mutex);

	sem = xSemaphoreCreateBinary();
	ASSERT(sem);
	return 0;
}

void LedTask::run(LedTask *task) {
	task->run_();
}

void LedTask::run_() {
	while (true) {
		auto sleep_ticks = calculateSleepTick();

		xSemaphoreTake(sem, sleep_ticks);
		ASSERT(xSemaphoreTake(mutex,portMAX_DELAY) == pdTRUE);

		for (int i = 0; i < ARRAY_SIZE(leds); i++) {
			led_t &led = leds[i];
			if (led.state_id != led_state_id::NULLID) {
				bool done = false;
				switch (states[(int) led.state_id].ops) {
				case led_state_ops::ON:
					turnOn(led.id);
					led.state_id = led_state_id::NULLID;
					break;
				case led_state_ops::OFF:
					turnOff(led.id);
					led.state_id = led_state_id::NULLID;
					break;
				case led_state_ops::CLOSE_FOREVER:
					removeOldState(led, states[(int) led.state_id].close_id);
					turnOff(led.id);
					led.state_id = led_state_id::NULLID;
					break;
				case led_state_ops::FOREVER_LOOP:
				case led_state_ops::LOOP: {
					auto off_dur = states[(int) led.state_id].off_duration;
					auto on_dur = states[(int) led.state_id].on_duration;
					auto tms = (NOW() - led.start_time) / (on_dur + off_dur);
					auto rest_time = (NOW() - led.start_time)
							% (on_dur + off_dur);

					if (on_dur > 0 && rest_time < on_dur) {
						turnOn(led.id);
					} else {
						turnOff(led.id);
					}

					if (led_state_ops::LOOP == states[(int) led.state_id].ops
							&& tms >= states[(int) led.state_id].loops) {
						turnOff(led.id);
						led.state_id = led_state_id::NULLID;
					}
				}
					break;

				}

				if (led.state_id == led_state_id::NULLID) {
					// get
					auto id = getOldStateId(led);
					if (id != led_state_id::NULLID) {
						setState_2(id);
						removeOldState(led, id);
					}
				}
			}
		}

		ASSERT(xSemaphoreGive(mutex) == pdTRUE);
	}

}

} /* namespace task */
